ποΈGitΠ―ΡΠ°ποΈ
Node / meshtastic / Meshtastic-Android / files / core / database / src / commonMain / kotlin / org / meshtastic / core / database / dao / PacketDao.kt
Displaying Raw β’ Download
core/database/src/commonMain/kotlin/org/meshtastic/core/database/dao/PacketDao.kt 41414f832f938b655c97bc07509bc140ea16e2a3 (41414f83) Text, 38.61 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.database.dao
Tff7b72import T7ee787androidx.paging.PagingSource
Tff7b72import T7ee787androidx.room3.Dao
Tff7b72import T7ee787androidx.room3.Insert
Tff7b72import T7ee787androidx.room3.MapColumn
Tff7b72import T7ee787androidx.room3.OnConflictStrategy
Tff7b72import T7ee787androidx.room3.Query
Tff7b72import T7ee787androidx.room3.Transaction
Tff7b72import T7ee787androidx.room3.Update
Tff7b72import T7ee787androidx.room3.Upsert
Tff7b72import T7ee787kotlinx.coroutines.flow.Flow
Tff7b72import T7ee787okio.ByteString
Tff7b72import T7ee787org.meshtastic.core.common.util.nowMillis
Tff7b72import T7ee787org.meshtastic.core.database.DatabaseConstants.SQLITE_MAX_BIND_PARAMETERS
Tff7b72import T7ee787org.meshtastic.core.database.entity.ContactSettings
Tff7b72import T7ee787org.meshtastic.core.database.entity.Packet
Tff7b72import T7ee787org.meshtastic.core.database.entity.PacketEntity
Tff7b72import T7ee787org.meshtastic.core.database.entity.ReactionEntity
Tff7b72import T7ee787org.meshtastic.core.model.DataPacket
Tff7b72import T7ee787org.meshtastic.core.model.MessageStatus
Tff7b72import T7ee787org.meshtastic.core.model.NodeAddress
Tff7b72import T7ee787org.meshtastic.proto.ChannelSettings
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffTooManyFunctionsTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLargeClassTa5d6ff"Tb4b4b4)
Tf0883e@Dao
Tff7b72interface T56d364PacketDao Tb4b4b4{
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = :portNum
ORDER BY received_time ASC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetAllPacketsTb4b4b4(Te6edf3portNumTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3PacketTff7b72>Tff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND filtered = 0
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetContactKeysTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<
Te6edf3MapTff7b72<
Tf0883e@MapColumnTb4b4b4(Te6edf3columnName Tff7b72= Ta5d6ff"Ta5d6ffcontact_keyTa5d6ff"Tb4b4b4)
Tffa657StringTb4b4b4,
Te6edf3PacketTb4b4b4,
Tff7b72>Tb4b4b4,
Tff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT p.* FROM packet p
INNER JOIN (
SELECT contact_key, MAX(received_time) as max_time
FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND filtered = 0
GROUP BY contact_key
) latest ON p.contact_key = latest.contact_key AND p.received_time = latest.max_time
WHERE (p.myNodeNum = 0 OR p.myNodeNum = (SELECT myNodeNum FROM my_node))
AND p.port_num = 1 AND p.filtered = 0
GROUP BY p.contact_key
ORDER BY p.received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetContactKeysPagedTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3PagingSourceTff7b72<Tffa657IntTb4b4b4, Te6edf3PacketTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetMessageCountTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Int
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND filtered = 0
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetUnreadCountTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Int
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND filtered = 0
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetUnreadCountFlowTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Tffa657IntTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT uuid FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND filtered = 0
ORDER BY received_time ASC
LIMIT 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetFirstUnreadMessageUuidTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Tffa657Long?Tff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) > 0 FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND filtered = 0
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffhasUnreadMessagesTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Tffa657BooleanTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND read = 0 AND filtered = 0
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetUnreadCountTotalTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Tffa657IntTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff UPDATE packet
SET read = 1
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND read = 0 AND filtered = 0 AND received_time <= :timestamp
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffclearUnreadCountTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3timestampTb4b4b4: Tffa657LongTb4b4b4)
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff UPDATE packet
SET read = 1
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND read = 0 AND filtered = 0
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffclearAllUnreadCountsTb4b4b4(Tb4b4b4)
Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffinsertTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3PacketTb4b4b4)
T8b949e/** Inserts a new packet row and returns its auto-generated stable database UUID. */
Tf0883e@Insert Tff7b72suspend Tff7b72fun Td2a8ffinsertAndGetIdTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3PacketTb4b4b4)Tb4b4b4: Tffa657Long
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetMessagesFromTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>Tff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
ORDER BY received_time DESC
LIMIT :limit
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetMessagesFromTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3limitTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>Tff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
AND (filtered = 0 OR :includeFiltered = 1)
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetMessagesFromTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3includeFilteredTb4b4b4: Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>Tff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetMessagesFromPagedTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3PagingSourceTff7b72<Tffa657IntTb4b4b4, Te6edf3PacketEntityTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND data = :data
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindDataPacketTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3DataPacketTb4b4b4)Tb4b4b4: Te6edf3Packet?
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM packet WHERE uuid in (:uuidList)Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeletePacketsTb4b4b4(Te6edf3uuidListTb4b4b4: Te6edf3ListTff7b72<Tffa657LongTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff DELETE FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND contact_key IN (:contactList)
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteContactsTb4b4b4(Te6edf3contactListTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM packet WHERE uuid=:uuidTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffdeleteTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3PacketTb4b4b4) Tb4b4b4{
Te6edf3deleteTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3uuidTb4b4b4)
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT packet_id FROM packet WHERE uuid IN (:uuidList)Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketIdsFromTb4b4b4(Te6edf3uuidListTb4b4b4: Te6edf3ListTff7b72<Tffa657LongTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff DELETE FROM reactions
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND reply_id IN (:packetIds)
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteReactionsTb4b4b4(Te6edf3packetIdsTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffdeleteMessagesTb4b4b4(Te6edf3uuidListTb4b4b4: Te6edf3ListTff7b72<Tffa657LongTff7b72>Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packetIds Tff7b72= Te6edf3getPacketIdsFromTb4b4b4(Te6edf3uuidListTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3packetIdsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3deleteReactionsTb4b4b4(Te6edf3packetIdsTb4b4b4)
Tb4b4b4}
Te6edf3deletePacketsTb4b4b4(Te6edf3uuidListTb4b4b4)
Tb4b4b4}
Tf0883e@Update Tff7b72suspend Tff7b72fun Td2a8ffupdateTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3PacketTb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateMessageStatusTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3DataPacketTb4b4b4, Te6edf3mTb4b4b4: Te6edf3MessageStatusTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3new Tff7b72= Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3mTb4b4b4)
T8b949e// Match on key fields that identify the packet, rather than the entire data object
Te6edf3findPacketsWithIdTb4b4b4(Te6edf3dataTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3id Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3from Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3from Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3to Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3updateTb4b4b4(Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3newTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateMessageStatusByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4) Tb4b4b4{
Te6edf3getPacketByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3uuidTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3updateTb4b4b4(Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Atomically claims one stable packet row for sending. A returned QUEUED packet means this call performed the
* QUEUED -> ENROUTE transition and owns the send; any other returned status means another path already handled it.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffclaimQueuedPacketTb4b4b4(Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Te6edf3Packet? Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3getPacketByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3uuidTb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3status Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3QUEUEDTb4b4b4) Tb4b4b4{
Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTETb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3packet
Tb4b4b4}
T8b949e/** Legacy claim used only by pre-upgrade WorkManager jobs whose mesh packet ID still resolves to one row. */
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffclaimQueuedPacketByPacketIdIfUniqueTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Packet? Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3findPacketsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4)Tb4b4b4.Te6edf3singleOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3status Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3QUEUEDTb4b4b4) Tb4b4b4{
Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTETb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3packet
Tb4b4b4}
T8b949e/** Rolls back a failed send only while the exact claimed row is still ENROUTE, preserving any racing ACK update. */
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffrollbackEnroutePacketTb4b4b4(Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72val Te6edf3packet Tff7b72= Te6edf3getPacketByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3uuidTb4b4b4)
Tff7b72val Te6edf3canRollback Tff7b72= Te6edf3packetTff7b72?.Te6edf3dataTff7b72?.Te6edf3status Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTE
Tff7b72if Tb4b4b4(Te6edf3canRollbackTb4b4b4) Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3QUEUEDTb4b4b4)Tb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3canRollback
Tb4b4b4}
T8b949e/**
* Resolves only an unambiguous row matching the identity available on an outgoing protobuf packet. Mesh packet IDs
* are sender-scoped, so ID-only lookup can select an inbound packet or an unrelated command with the same ID.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffresolveOutgoingPacketTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4)Tb4b4b4: Te6edf3Packet? Tff7b72=
Te6edf3outgoingCandidatesTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3findPacketsWithIdTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3singleOrNullTb4b4b4(Tb4b4b4)
T8b949e/**
* Resolves and conditionally applies a queue-stage status without racing a terminal ACK/NAK update.
*
* @return the resolved row as read before the update, or null when no unambiguous outgoing row matches.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffapplyOutgoingQueueStatusTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4)Tb4b4b4: Te6edf3Packet? Tb4b4b4{
Tff7b72val Te6edf3match Tff7b72= Te6edf3resolveOutgoingPacketTb4b4b4(Te6edf3packetTb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3shouldApplyOutgoingQueueStatusTb4b4b4(Te6edf3matchTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3statusTb4b4b4, Te6edf3statusTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3updateTb4b4b4(Te6edf3matchTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3matchTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3match
Tb4b4b4}
T8b949e/**
* Reaction equivalent of [applyOutgoingQueueStatus], restricted to one unambiguous outgoing row.
*
* @return the resolved reaction as read before the update, or null when no unambiguous non-received row matches.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffapplyOutgoingReactionQueueStatusTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4)Tb4b4b4: Te6edf3ReactionEntity? Tb4b4b4{
Tff7b72val Te6edf3match Tff7b72=
Te6edf3findReactionsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3status Tff7b72!Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3RECEIVED Tb4b4b4}Tb4b4b4.Te6edf3singleOrNullTb4b4b4(Tb4b4b4) Tff7b72?: Tff7b72return Tff7b72null
Tff7b72if Tb4b4b4(Te6edf3shouldApplyOutgoingQueueStatusTb4b4b4(Te6edf3matchTb4b4b4.Te6edf3statusTb4b4b4, Te6edf3statusTb4b4b4)Tb4b4b4) Te6edf3updateTb4b4b4(Te6edf3matchTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4)Tb4b4b4)
Tff7b72return Te6edf3match
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffoutgoingCandidatesTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4, Te6edf3storedTb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72> Tb4b4b4{
Tff7b72val Te6edf3portNum Tff7b72= Te6edf3packetTb4b4b4.Te6edf3decodedTff7b72?.Te6edf3portnumTff7b72?.Te6edf3value
Tff7b72return Te6edf3storedTb4b4b4.Te6edf3filter Tb4b4b4{
Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3fromTb4b4b4.Te6edf3matchesNodeNumTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4) Tff7b72&Tff7b72&
Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3toTb4b4b4.Te6edf3matchesNodeNumTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3toTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3fromTb4b4b4) Tff7b72&Tff7b72&
Tb4b4b4(Te6edf3portNum Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3dataType Tff7b72=Tff7b72= Te6edf3portNumTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/** Updates the unique outgoing row, preferring the only candidate already at [status] when duplicates exist. */
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateOutgoingMessageStatusTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3MeshPacketTb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4)Tb4b4b4: Te6edf3Packet? Tb4b4b4{
Tff7b72val Te6edf3matches Tff7b72= Te6edf3outgoingCandidatesTb4b4b4(Te6edf3packetTb4b4b4, Te6edf3findPacketsWithIdTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3idTb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3alreadyAtStatus Tff7b72= Te6edf3matchesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3status Tff7b72=Tff7b72= Te6edf3status Tb4b4b4}
Tff7b72val Te6edf3match Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3alreadyAtStatusTb4b4b4.Te6edf3size Tff7b72=Tff7b72= T79c0ff1 Tff7b72-Tff7b72> Te6edf3alreadyAtStatusTb4b4b4.Te6edf3singleTb4b4b4(Tb4b4b4)
Te6edf3alreadyAtStatusTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tff7b72null
Tff7b72else Tff7b72-Tff7b72> Te6edf3matchesTb4b4b4.Te6edf3singleOrNullTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3matchTff7b72?.Te6edf3also Tb4b4b4{ Tff7b72if Tb4b4b4(Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3status Tff7b72!Tff7b72= Te6edf3statusTb4b4b4) Te6edf3updateTb4b4b4(Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4)Tb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateMessageIdTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3DataPacketTb4b4b4, Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3new Tff7b72= Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3id Tff7b72= Te6edf3idTb4b4b4)
T8b949e// Match on key fields that identify the packet
Te6edf3findPacketsWithIdTb4b4b4(Te6edf3dataTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3id Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3from Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3from Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3to Tb4b4b4}
Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3updateTb4b4b4(Tffa657itTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3newTb4b4b4, Te6edf3packetId Tff7b72= Te6edf3idTb4b4b4)Tb4b4b4) Tb4b4b4}
Tb4b4b4}
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT data FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
ORDER BY received_time ASC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetDataPacketsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DataPacketTff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND packet_id = :requestId
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketByIdTb4b4b4(Te6edf3requestIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Packet?
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE packet_id = :packetId
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
LIMIT 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketByPacketIdTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3PacketEntity?
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE packet_id = :packetId
AND contact_key = :contactKey
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketsByPacketIdAndContactTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4, Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE uuid = :uuid
AND myNodeNum = :myNodeNum
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
LIMIT 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Te6edf3Packet?
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE packet_id IN (:packetIds)
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketsByPacketIdsTb4b4b4(Te6edf3packetIdsTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE packet_id IN (:packetIds)
AND contact_key = :contactKey
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketsByPacketIdsAndContactTb4b4b4(Te6edf3packetIdsTb4b4b4: Te6edf3ListTff7b72<Tffa657IntTff7b72>Tb4b4b4, Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketEntityTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE packet_id = :packetId
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindPacketsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND substr(sfpp_hash, 1, 8) = substr(:hash, 1, 8)
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindPacketBySfppHashTb4b4b4(Te6edf3hashTb4b4b4: Te6edf3ByteStringTb4b4b4)Tb4b4b4: Te6edf3Packet?
T8b949e// Fetches all DataPackets for the current node, ordered by time.
T8b949e// Callers should filter by status in Kotlin (avoids SQLite json_extract dependency).
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT data FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
ORDER BY received_time ASC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllDataPacketsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3DataPacketTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
ORDER BY received_time ASC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllPersistedPacketsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 8
ORDER BY received_time ASC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllWaypointsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffdeleteWaypointTb4b4b4(Te6edf3idTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3uuidList Tff7b72= Te6edf3getAllWaypointsTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3waypointTff7b72?.Te6edf3id Tff7b72=Tff7b72= Te6edf3id Tb4b4b4}Tb4b4b4.Te6edf3map Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3uuid Tb4b4b4}
Te6edf3deleteMessagesTb4b4b4(Te6edf3uuidListTb4b4b4)
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM contact_settingsTa5d6ff"Tb4b4b4)
Tff7b72fun Td2a8ffgetContactSettingsTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<
Te6edf3MapTff7b72<
Tf0883e@MapColumnTb4b4b4(Te6edf3columnName Tff7b72= Ta5d6ff"Ta5d6ffcontact_keyTa5d6ff"Tb4b4b4)
Tffa657StringTb4b4b4,
Te6edf3ContactSettingsTb4b4b4,
Tff7b72>Tb4b4b4,
Tff7b72>
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM contact_settings WHERE contact_key = :contactTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetContactSettingsTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ContactSettings?
Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffupsertContactSettingsTb4b4b4(Te6edf3contactsTb4b4b4: Te6edf3ListTff7b72<Te6edf3ContactSettingsTff7b72>Tb4b4b4)
Tf0883e@InsertTb4b4b4(Te6edf3onConflict Tff7b72= Te6edf3OnConflictStrategyTb4b4b4.Te6edf3IGNORETb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffinsertContactSettingsIgnoreTb4b4b4(Te6edf3contactsTb4b4b4: Te6edf3ListTff7b72<Te6edf3ContactSettingsTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE contact_settings SET muteUntil = :muteUntil WHERE contact_key IN (:contactKeys)Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffupdateMuteUntilTb4b4b4(Te6edf3contactKeysTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4, Te6edf3muteUntilTb4b4b4: Tffa657LongTb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffsetMuteUntilTb4b4b4(Te6edf3contactsTb4b4b4: Te6edf3ListTff7b72<Tffa657StringTff7b72>Tb4b4b4, Te6edf3untilTb4b4b4: Tffa657LongTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3absoluteMuteUntil Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3until Tff7b72=Tff7b72= Tffa657LongTb4b4b4.Te6edf3MAX_VALUE Tff7b72-Tff7b72> Tffa657LongTb4b4b4.Te6edf3MAX_VALUE
Te6edf3until Tff7b72=Tff7b72= T79c0ff0L Tff7b72-Tff7b72> T79c0ff0L
Tff7b72else Tff7b72-Tff7b72> Te6edf3nowMillis Tff7b72+ Te6edf3until
Tb4b4b4}
T8b949e// Ensure rows exist for all contacts (IGNORE avoids overwriting existing data)
Te6edf3insertContactSettingsIgnoreTb4b4b4(Te6edf3contactsTb4b4b4.Te6edf3map Tb4b4b4{ Te6edf3ContactSettingsTb4b4b4(Te6edf3contact_key Tff7b72= Tffa657itTb4b4b4) Tb4b4b4}Tb4b4b4)
T8b949e// Atomic column-level update β no read-then-write race
Te6edf3updateMuteUntilTb4b4b4(Te6edf3contactsTb4b4b4, Te6edf3absoluteMuteUntilTb4b4b4)
Tb4b4b4}
Tf0883e@Upsert Tff7b72suspend Tff7b72fun Td2a8ffinsertTb4b4b4(Te6edf3reactionTb4b4b4: Te6edf3ReactionEntityTb4b4b4)
Tf0883e@Update Tff7b72suspend Tff7b72fun Td2a8ffupdateTb4b4b4(Te6edf3reactionTb4b4b4: Te6edf3ReactionEntityTb4b4b4)
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM reactions
WHERE packet_id = :packetId
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindReactionsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3ReactionEntityTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM reactions
WHERE packet_id = :packetId
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
LIMIT 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetReactionByPacketIdTb4b4b4(Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ReactionEntity?
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM reactions
WHERE status = :status
AND (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetReactionsByStatusTb4b4b4(Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3ReactionEntityTff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM reactions
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND substr(sfpp_hash, 1, 8) = substr(:hash, 1, 8)
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8fffindReactionBySfppHashTb4b4b4(Te6edf3hashTb4b4b4: Te6edf3ByteStringTb4b4b4)Tb4b4b4: Te6edf3ReactionEntity?
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND filtered = 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetFilteredCountTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Int
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT COUNT(*) FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact AND filtered = 1
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetFilteredCountFlowTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3FlowTff7b72<Tffa657IntTff7b72>
Tf0883e@Transaction
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff SELECT * FROM packet
WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node))
AND port_num = 1 AND contact_key = :contact
AND (filtered = 0 OR :includeFiltered = 1)
ORDER BY received_time DESC
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72fun Td2a8ffgetMessagesFromPagedTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3includeFilteredTb4b4b4: Tffa657BooleanTb4b4b4)Tb4b4b4: Te6edf3PagingSourceTff7b72<Tffa657IntTb4b4b4, Te6edf3PacketEntityTff7b72>
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT filtering_disabled FROM contact_settings WHERE contact_key = :contactTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetContactFilteringDisabledTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean?
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffsetContactFilteringDisabledTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3disabledTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3settings Tff7b72=
Te6edf3getContactSettingsTb4b4b4(Te6edf3contactTb4b4b4)Tff7b72?.Te6edf3copyTb4b4b4(Te6edf3filteringDisabled Tff7b72= Te6edf3disabledTb4b4b4)
Tff7b72?: Te6edf3ContactSettingsTb4b4b4(Te6edf3contact_key Tff7b72= Te6edf3contactTb4b4b4, Te6edf3filteringDisabled Tff7b72= Te6edf3disabledTb4b4b4)
Te6edf3upsertContactSettingsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3settingsTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffdeleteAllTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3deleteAllPacketsTb4b4b4(Tb4b4b4)
Te6edf3deleteAllReactionsTb4b4b4(Tb4b4b4)
Te6edf3deleteAllContactSettingsTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM packetTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteAllPacketsTb4b4b4(Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM reactionsTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteAllReactionsTb4b4b4(Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffDELETE FROM contact_settingsTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffdeleteAllContactSettingsTb4b4b4(Tb4b4b4)
T8b949e// region ββ Cross-transport merge ββ
T8b949e// Snapshots + inserts used by DatabaseMerger to fold one transport's DB into another for the same node.
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM packetTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllPacketsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
T8b949e/** Insert a packet copied from another DB. Pass uuid = 0 so a fresh auto-generated id is assigned. */
Tf0883e@Insert Tff7b72suspend Tff7b72fun Td2a8ffinsertPacketForMergeTb4b4b4(Te6edf3packetTb4b4b4: Te6edf3PacketTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM reactionsTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllReactionsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3ReactionEntityTff7b72>
Tf0883e@InsertTb4b4b4(Te6edf3onConflict Tff7b72= Te6edf3OnConflictStrategyTb4b4b4.Te6edf3IGNORETb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffinsertReactionsIgnoreTb4b4b4(Te6edf3reactionsTb4b4b4: Te6edf3ListTff7b72<Te6edf3ReactionEntityTff7b72>Tb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM contact_settingsTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllContactSettingsSnapshotTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3ContactSettingsTff7b72>
T8b949e// endregion
T8b949e/**
* One-time migration: Remap all message DataPacket.channel indices to new mapping using PSK after a channel
* reorder. For each Packet (with port_num = 1), finds the old PSK then sets the channel index to the matching
* newSettings index. Skips if PSKs do not match or are missing.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffmigrateChannelsByPSKTb4b4b4(Te6edf3oldSettingsTb4b4b4: Te6edf3ListTff7b72<Te6edf3ChannelSettingsTff7b72>Tb4b4b4, Te6edf3newSettingsTb4b4b4: Te6edf3ListTff7b72<Te6edf3ChannelSettingsTff7b72>Tb4b4b4) Tb4b4b4{
T8b949e// Pre-calculate mapping from old index to new index
Tff7b72val Te6edf3indexMap Tff7b72=
Te6edf3oldSettings
Tb4b4b4.Te6edf3mapIndexed Tb4b4b4{ Te6edf3oldIndexTb4b4b4, Te6edf3oldChannel Tff7b72-Tff7b72>
Tff7b72val Te6edf3pskMatches Tff7b72=
Te6edf3newSettingsTb4b4b4.Te6edf3mapIndexedNotNull Tb4b4b4{ Te6edf3indexTb4b4b4, Te6edf3channel Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3channelTb4b4b4.Te6edf3psk Tff7b72=Tff7b72= Te6edf3oldChannelTb4b4b4.Te6edf3pskTb4b4b4) Te6edf3index Te6edf3to Te6edf3channel Tff7b72else Tff7b72null
Tb4b4b4}
Tff7b72val Te6edf3newIndex Tff7b72=
Tff7b72when Tb4b4b4{
Te6edf3pskMatchesTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4) Tff7b72-Tff7b72> Tff7b72null
Te6edf3pskMatchesTb4b4b4.Te6edf3size Tff7b72=Tff7b72= T79c0ff1 Tff7b72-Tff7b72> Te6edf3pskMatchesTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first
Tff7b72else Tff7b72-Tff7b72> Tb4b4b4{
T8b949e// Multiple matches with same PSK. Disambiguate by Name.
Tff7b72val Te6edf3nameMatches Tff7b72= Te6edf3pskMatchesTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3secondTb4b4b4.Te6edf3name Tff7b72=Tff7b72= Te6edf3oldChannelTb4b4b4.Te6edf3name Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3nameMatchesTb4b4b4.Te6edf3size Tff7b72=Tff7b72= T79c0ff1Tb4b4b4) Tb4b4b4{
Te6edf3nameMatchesTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first
Tb4b4b4} Tff7b72else Tb4b4b4{
T8b949e// Still ambiguous. Prefer keeping same index.
Te6edf3pskMatchesTb4b4b4.Te6edf3find Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3first Tff7b72=Tff7b72= Te6edf3oldIndex Tb4b4b4}Tff7b72?.Te6edf3first Tff7b72?: Te6edf3pskMatchesTb4b4b4.Te6edf3firstTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3first
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Te6edf3oldIndex Te6edf3to Te6edf3newIndex
Tb4b4b4}
Tb4b4b4.Te6edf3toMapTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3allPackets Tff7b72= Te6edf3getAllUserPacketsForMigrationTb4b4b4(Tb4b4b4)
Tff7b72for Tb4b4b4(Te6edf3packet Tff7b72in Te6edf3allPacketsTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3oldIndex Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3channel
Tff7b72val Te6edf3newIndex Tff7b72= Te6edf3indexMapTff7b72[Te6edf3oldIndexTff7b72]
Tff7b72if Tb4b4b4(Te6edf3newIndex Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3oldIndex Tff7b72!Tff7b72= Te6edf3newIndexTb4b4b4) Tb4b4b4{
T8b949e// Rebuild contact_key with the new index, keeping the rest unchanged
Tff7b72val Te6edf3oldKeySuffix Tff7b72= Te6edf3packetTb4b4b4.Te6edf3contact_keyTb4b4b4.Te6edf3dropWhile Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3isDigitTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3newContactKey Tff7b72= Ta5d6ff"Tffd700$Te6edf3newIndexTffd700$Te6edf3oldKeySuffixTa5d6ff"
Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3contact_key Tff7b72= Te6edf3newContactKeyTb4b4b4, Te6edf3data Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3channel Tff7b72= Te6edf3newIndexTb4b4b4)Tb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM packet WHERE port_num = 1Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetAllUserPacketsForMigrationTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMaxLineLengthTa5d6ff"Tb4b4b4)
Tf0883e@QueryTb4b4b4(
Ta5d6ff"Ta5d6ffUPDATE packet SET filtered = :filtered WHERE (myNodeNum = 0 OR myNodeNum = (SELECT myNodeNum FROM my_node)) AND data LIKE :senderIdPatternTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffupdateFilteredBySenderTb4b4b4(Te6edf3senderIdPatternTb4b4b4: Tffa657StringTb4b4b4, Te6edf3filteredTb4b4b4: Tffa657BooleanTb4b4b4)
T8b949e/** Persists an on-device translation and switches the message to display it in one write. */
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE packet SET translated_text = :translatedText, show_translated = 1 WHERE uuid = :uuidTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffsetTranslationTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3translatedTextTb4b4b4: Tffa657StringTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE packet SET show_translated = :show WHERE uuid = :uuidTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffsetShowTranslatedTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3showTb4b4b4: Tffa657BooleanTb4b4b4)
T8b949e// region ββ Atomic read-modify-write transactions ββ
T8b949e/**
* Atomically updates the last-read message pointer for [contact], preserving the monotonic-timestamp guard: if
* [lastReadTimestamp] is not newer than the stored value, the method is a no-op. Creates the contact-settings row
* if absent. Uses INSERT OR IGNORE + conditional UPDATE instead of a read-then-@Upsert so the existing row's
* unrelated columns (muteUntil, filteringDisabled) are preserved without a Kotlin-side read-modify-write.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateLastReadMessageTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3messageUuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3lastReadTimestampTb4b4b4: Tffa657LongTb4b4b4) Tb4b4b4{
Te6edf3insertContactSettingsIgnoreTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3ContactSettingsTb4b4b4(Te6edf3contact_key Tff7b72= Te6edf3contactTb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3updateLastReadMessageIfNewerTb4b4b4(Te6edf3contactTb4b4b4, Te6edf3messageUuidTb4b4b4, Te6edf3lastReadTimestampTb4b4b4)
Tb4b4b4}
T8b949e/**
* Conditional UPDATE that only writes newer timestamps. Callers must ensure the contact-settings row exists (e.g.
* via [insertContactSettingsIgnore]) before calling this inside the same transaction.
*
* Returns the affected row count (1 = updated, 0 = no-op) for testability; callers outside tests typically ignore
* it.
*/
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff UPDATE contact_settings
SET last_read_message_uuid = :messageUuid,
last_read_message_timestamp = :lastReadTimestamp
WHERE contact_key = :contact
AND (
last_read_message_timestamp IS NULL
OR last_read_message_timestamp < :lastReadTimestamp
)
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffupdateLastReadMessageIfNewerTb4b4b4(Te6edf3contactTb4b4b4: Tffa657StringTb4b4b4, Te6edf3messageUuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3lastReadTimestampTb4b4b4: Tffa657LongTb4b4b4)Tb4b4b4: Tffa657Int
T8b949e/**
* Atomically finds a packet by identity key (id + from + to) and updates its data, optionally stamping
* [routingError] when it is non-negative. Mirrors the existing [updateMessageStatus] / [updateMessageId] pattern.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdatePacketByKeyTb4b4b4(Te6edf3dataTb4b4b4: Te6edf3DataPacketTb4b4b4, Te6edf3routingErrorTb4b4b4: Tffa657IntTb4b4b4) Tb4b4b4{
Te6edf3findPacketsWithIdTb4b4b4(Te6edf3dataTb4b4b4.Te6edf3idTb4b4b4)
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3id Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3id Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3from Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3from Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3dataTb4b4b4.Te6edf3to Tb4b4b4}
Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3existing Tff7b72-Tff7b72>
Tff7b72val Te6edf3updated Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3routingError Tff7b72>Tff7b72= T79c0ff0Tb4b4b4) Tb4b4b4{
Te6edf3existingTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3dataTb4b4b4, Te6edf3routingError Tff7b72= Te6edf3routingErrorTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3existingTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3dataTb4b4b4)
Tb4b4b4}
Te6edf3updateTb4b4b4(Te6edf3updatedTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Stamps [routingError] on a sent packet only while it is still [MessageStatus.ENROUTE]. The read and the write
* share one transaction so an ACK/NAK that resolves the packet concurrently is never overwritten by a send-ack
* timeout that sampled the row before it landed.
*
* @return true if a row was timed out.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8fftimeOutEnroutePacketTb4b4b4(Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3routingErrorTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Tff7b72val Te6edf3existing Tff7b72= Te6edf3getPacketByPersistedIdTb4b4b4(Te6edf3myNodeNumTb4b4b4, Te6edf3uuidTb4b4b4)
Tff7b72val Te6edf3canTimeOut Tff7b72= Te6edf3existingTff7b72?.Te6edf3dataTff7b72?.Te6edf3status Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTE
Tff7b72if Tb4b4b4(Te6edf3canTimeOutTb4b4b4) Tb4b4b4{
Te6edf3updateTb4b4b4(Te6edf3existingTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3existingTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ERRORTb4b4b4)Tb4b4b4, Te6edf3routingError Tff7b72= Te6edf3routingErrorTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72return Te6edf3canTimeOut
Tb4b4b4}
Tf0883e@QueryTb4b4b4(
Ta5d6ff"""
Ta5d6ff UPDATE reactions
SET status = :failedStatus, routing_error = :routingError
WHERE myNodeNum = :myNodeNum
AND reply_id = :replyId
AND user_id = :userId
AND emoji = :emoji
AND status = :enrouteStatus
Ta5d6ff"""Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffupdateEnrouteReactionStatusTb4b4b4(
Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3replyIdTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3userIdTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3emojiTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3routingErrorTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3enrouteStatusTb4b4b4: Te6edf3MessageStatusTb4b4b4,
Te6edf3failedStatusTb4b4b4: Te6edf3MessageStatusTb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657Int
T8b949e/**
* Atomically stamps [routingError] on a sent reaction only while it is still [MessageStatus.ENROUTE].
*
* @return true if a row was timed out.
*/
Tff7b72suspend Tff7b72fun Td2a8fftimeOutEnrouteReactionTb4b4b4(
Te6edf3myNodeNumTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3replyIdTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3userIdTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3emojiTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3routingErrorTb4b4b4: Tffa657IntTb4b4b4,
Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Te6edf3updateEnrouteReactionStatusTb4b4b4(
Te6edf3myNodeNum Tff7b72= Te6edf3myNodeNumTb4b4b4,
Te6edf3replyId Tff7b72= Te6edf3replyIdTb4b4b4,
Te6edf3userId Tff7b72= Te6edf3userIdTb4b4b4,
Te6edf3emoji Tff7b72= Te6edf3emojiTb4b4b4,
Te6edf3routingError Tff7b72= Te6edf3routingErrorTb4b4b4,
Te6edf3enrouteStatus Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTETb4b4b4,
Te6edf3failedStatus Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ERRORTb4b4b4,
Tb4b4b4) Tff7b72=Tff7b72= T79c0ff1
T8b949e/**
* Atomically finds reactions by [replacement]'s packetId + userId + emoji and updates every ownership-scoped copy,
* borrowing [myNodeNum][ReactionEntity.myNodeNum] from each existing row. No-op if no match is found.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffupdateReactionByKeyTb4b4b4(Te6edf3replacementTb4b4b4: Te6edf3ReactionEntityTb4b4b4) Tb4b4b4{
Te6edf3findReactionsWithIdTb4b4b4(Te6edf3replacementTb4b4b4.Te6edf3packetIdTb4b4b4)
Tb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3userId Tff7b72=Tff7b72= Te6edf3replacementTb4b4b4.Te6edf3userId Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3emoji Tff7b72=Tff7b72= Te6edf3replacementTb4b4b4.Te6edf3emoji Tb4b4b4}
Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3existing Tff7b72-Tff7b72>
T8b949e// myNodeNum is part of the composite PK and must come from each existing row.
Te6edf3updateTb4b4b4(Te6edf3replacementTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3myNodeNum Tff7b72= Te6edf3existingTb4b4b4.Te6edf3myNodeNumTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// ββ SFPP helpers: shared no-downgrade guard + timestamp resolution used by both applySFPPStatus and
T8b949e// applySFPPStatusByHash. Extracted so a future status-guard change only touches one predicate.
Tff7b72private Tff7b72fun Te6edf3MessageStatusTb4b4b4.Td2a8ffisDowngradeFromTb4b4b4(Te6edf3currentTb4b4b4: Te6edf3MessageStatus?Tb4b4b4) Tff7b72=
Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3SFPP_CONFIRMED Tff7b72&Tff7b72& Tff7b72this Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3SFPP_ROUTING
Tff7b72private Tff7b72fun Td2a8ffresolveNewTimeTb4b4b4(Te6edf3rxTimeTb4b4b4: Tffa657LongTb4b4b4, Te6edf3fallbackTb4b4b4: Tffa657LongTb4b4b4) Tff7b72= Tff7b72if Tb4b4b4(Te6edf3rxTime Tff7b72> T79c0ff0Tb4b4b4) Te6edf3rxTime Tff7b72* Te6edf3MILLIS_PER_SECOND Tff7b72else Te6edf3fallback
T8b949e/**
* Atomically applies an SFPP delivery-status transition to every packet and reaction matching [packetId] + address
* ([from]/[to]). Preserves the no-downgrade invariant: an item already
* [SFPP_CONFIRMED][MessageStatus.SFPP_CONFIRMED] is not regressed to [SFPP_ROUTING][MessageStatus.SFPP_ROUTING].
* All updates land in one transaction so a packet and its reactions cannot end up in inconsistent delivery states.
*/
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffCyclomaticComplexMethodTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffLongParameterListTa5d6ff"Tb4b4b4)
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffapplySFPPStatusTb4b4b4(
Te6edf3packetIdTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3fromTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3toTb4b4b4: Tffa657IntTb4b4b4,
Te6edf3hashTb4b4b4: Te6edf3ByteStringTb4b4b4,
Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4,
Te6edf3rxTimeTb4b4b4: Tffa657LongTb4b4b4,
Te6edf3myNodeNumTb4b4b4: Tffa657Int?Tb4b4b4,
Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3packets Tff7b72= Te6edf3findPacketsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4)
Tff7b72val Te6edf3reactions Tff7b72= Te6edf3findReactionsWithIdTb4b4b4(Te6edf3packetIdTb4b4b4)
Tff7b72val Te6edf3fromId Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3fromTb4b4b4)
Tff7b72val Te6edf3isFromLocalNode Tff7b72= Te6edf3myNodeNum Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Te6edf3from Tff7b72=Tff7b72= Te6edf3myNodeNum
Tff7b72val Te6edf3toId Tff7b72=
Tff7b72if Tb4b4b4(Te6edf3to Tff7b72=Tff7b72= T79c0ff0 Tff7b72|Tff7b72| Te6edf3to Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3NODENUM_BROADCASTTb4b4b4) Tb4b4b4{
Te6edf3NodeAddressTb4b4b4.Te6edf3ID_BROADCAST
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3NodeAddressTb4b4b4.Te6edf3numToDefaultIdTb4b4b4(Te6edf3toTb4b4b4)
Tb4b4b4}
Te6edf3packetsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3packet Tff7b72-Tff7b72>
Tff7b72val Te6edf3fromMatches Tff7b72=
Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3from Tff7b72=Tff7b72= Te6edf3fromId Tff7b72|Tff7b72| Tb4b4b4(Te6edf3isFromLocalNode Tff7b72&Tff7b72& Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3from Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_LOCALTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3fromMatches Tff7b72&Tff7b72& Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3toIdTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3statusTb4b4b4.Te6edf3isDowngradeFromTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3statusTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@forEach
Tff7b72val Te6edf3newTime Tff7b72= Te6edf3resolveNewTimeTb4b4b4(Te6edf3rxTimeTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3received_timeTb4b4b4)
Tff7b72val Te6edf3updatedData Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4, Te6edf3sfppHash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3time Tff7b72= Te6edf3newTimeTb4b4b4)
Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3updatedDataTb4b4b4, Te6edf3sfpp_hash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3received_time Tff7b72= Te6edf3newTimeTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Te6edf3reactionsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3reaction Tff7b72-Tff7b72>
Tff7b72val Te6edf3fromMatches Tff7b72= Te6edf3reactionTb4b4b4.Te6edf3userId Tff7b72=Tff7b72= Te6edf3fromId Tff7b72|Tff7b72| Tb4b4b4(Te6edf3isFromLocalNode Tff7b72&Tff7b72& Te6edf3reactionTb4b4b4.Te6edf3userId Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3ID_LOCALTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3fromMatches Tff7b72&Tff7b72& Tb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3reactionTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3toIdTb4b4b4)Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3statusTb4b4b4.Te6edf3isDowngradeFromTb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3statusTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@forEach
Tff7b72val Te6edf3newTime Tff7b72= Te6edf3resolveNewTimeTb4b4b4(Te6edf3rxTimeTb4b4b4, Te6edf3reactionTb4b4b4.Te6edf3timestampTb4b4b4)
Te6edf3updateTb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4, Te6edf3sfpp_hash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3newTimeTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Atomically applies an SFPP delivery-status transition to the single packet and single reaction matching [hash]
* (8-byte prefix). Same no-downgrade invariant as [applySFPPStatus]. Both updates land in one transaction.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffapplySFPPStatusByHashTb4b4b4(Te6edf3hashTb4b4b4: Te6edf3ByteStringTb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4, Te6edf3rxTimeTb4b4b4: Tffa657LongTb4b4b4) Tb4b4b4{
Te6edf3findPacketBySfppHashTb4b4b4(Te6edf3hashTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3packet Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3statusTb4b4b4.Te6edf3isDowngradeFromTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3statusTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@let
Tff7b72val Te6edf3newTime Tff7b72= Te6edf3resolveNewTimeTb4b4b4(Te6edf3rxTimeTb4b4b4, Te6edf3packetTb4b4b4.Te6edf3received_timeTb4b4b4)
Tff7b72val Te6edf3updatedData Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4, Te6edf3sfppHash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3time Tff7b72= Te6edf3newTimeTb4b4b4)
Te6edf3updateTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3data Tff7b72= Te6edf3updatedDataTb4b4b4, Te6edf3sfpp_hash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3received_time Tff7b72= Te6edf3newTimeTb4b4b4)Tb4b4b4)
Tb4b4b4}
Te6edf3findReactionBySfppHashTb4b4b4(Te6edf3hashTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3reaction Tff7b72-Tff7b72>
Tff7b72if Tb4b4b4(Te6edf3statusTb4b4b4.Te6edf3isDowngradeFromTb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3statusTb4b4b4)Tb4b4b4) Tff7b72returnTf0883e@let
Tff7b72val Te6edf3newTime Tff7b72= Te6edf3resolveNewTimeTb4b4b4(Te6edf3rxTimeTb4b4b4, Te6edf3reactionTb4b4b4.Te6edf3timestampTb4b4b4)
Te6edf3updateTb4b4b4(Te6edf3reactionTb4b4b4.Te6edf3copyTb4b4b4(Te6edf3status Tff7b72= Te6edf3statusTb4b4b4, Te6edf3sfpp_hash Tff7b72= Te6edf3hashTb4b4b4, Te6edf3timestamp Tff7b72= Te6edf3newTimeTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Atomically deletes all messages (and their reactions) for the given [uuidList], chunking internally to stay under
* SQLite's bind-parameter limit. The entire batch is all-or-nothing: a failure rolls back every chunk.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffdeleteMessagesAtomicTb4b4b4(Te6edf3uuidListTb4b4b4: Te6edf3ListTff7b72<Tffa657LongTff7b72>Tb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3uuidListTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72return
Tff7b72for Tb4b4b4(Te6edf3chunk Tff7b72in Te6edf3uuidListTb4b4b4.Te6edf3chunkedTb4b4b4(Te6edf3SQLITE_MAX_BIND_PARAMETERSTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3deleteMessagesTb4b4b4(Te6edf3chunkTb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e// endregion
Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72private Tff7b72const Tff7b72val Te6edf3MILLIS_PER_SECOND Tff7b72= T79c0ff1T79c0ff0T79c0ff0T79c0ff0L
Tb4b4b4}
T8b949e// region ββ FTS5 Search ββ
Tf0883e@QueryTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT packet.* FROM packet JOIN packet_fts ON packet.rowid = packet_fts.rowid Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffWHERE packet_fts MATCH :query AND packet.myNodeNum = (SELECT myNodeNum FROM my_node) Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffORDER BY packet.received_time DESC LIMIT 100Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffsearchMessagesTb4b4b4(Te6edf3queryTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@QueryTb4b4b4(
Ta5d6ff"Ta5d6ffSELECT packet.* FROM packet JOIN packet_fts ON packet.rowid = packet_fts.rowid Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffWHERE packet_fts MATCH :query AND packet.contact_key = :contactKey Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffAND packet.myNodeNum = (SELECT myNodeNum FROM my_node) Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ffORDER BY packet.received_time DESC LIMIT 100Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffsearchMessagesInConversationTb4b4b4(Te6edf3queryTb4b4b4: Tffa657StringTb4b4b4, Te6edf3contactKeyTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffUPDATE packet SET message_text = :text WHERE uuid = :uuidTa5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffupdateMessageTextTb4b4b4(Te6edf3uuidTb4b4b4: Tffa657LongTb4b4b4, Te6edf3textTb4b4b4: Tffa657StringTb4b4b4)
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT COUNT(*) FROM packet WHERE port_num = 1 AND (message_text IS NULL OR message_text = '')Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffcountPacketsNeedingBackfillTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffSELECT * FROM packet WHERE port_num = 1 AND (message_text IS NULL OR message_text = '')Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffgetPacketsNeedingBackfillTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3ListTff7b72<Te6edf3PacketTff7b72>
T8b949e/**
* Populates [Packet.messageText] for historical text packets that predate the FTS5 schema (v39) so they become
* searchable. The text is decoded in Kotlin from each packet's [DataPacket.text]; it cannot be read with a SQL
* `json_extract(data, '$.text')` because [DataPacket.text] is a computed property that is never serialized into the
* stored JSON (the payload is persisted as `bytes`). Returns the number of rows updated; the caller rebuilds the
* FTS index via [rebuildFtsIndex] when this is greater than zero.
*/
Tf0883e@Transaction
Tff7b72suspend Tff7b72fun Td2a8ffbackfillMessageTextsTb4b4b4(Tb4b4b4)Tb4b4b4: Tffa657Int Tb4b4b4{
Tff7b72var Te6edf3updated Tff7b72= T79c0ff0
Te6edf3getPacketsNeedingBackfillTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3packet Tff7b72-Tff7b72>
Tff7b72val Te6edf3text Tff7b72= Te6edf3packetTb4b4b4.Te6edf3dataTb4b4b4.Te6edf3text
Tff7b72if Tb4b4b4(Tff7b72!Te6edf3textTb4b4b4.Te6edf3isNullOrEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3updateMessageTextTb4b4b4(Te6edf3packetTb4b4b4.Te6edf3uuidTb4b4b4, Te6edf3textTb4b4b4)
Te6edf3updatedTff7b72+Tff7b72+
Tb4b4b4}
Tb4b4b4}
Tff7b72return Te6edf3updated
Tb4b4b4}
Tf0883e@QueryTb4b4b4(Ta5d6ff"Ta5d6ffINSERT INTO packet_fts(packet_fts) VALUES('rebuild')Ta5d6ff"Tb4b4b4)
Tff7b72suspend Tff7b72fun Td2a8ffrebuildFtsIndexTb4b4b4(Tb4b4b4)
T8b949e// endregion
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffStringTff7b72?.Te6edf3matchesNodeNumTb4b4b4(Te6edf3nodeNumTb4b4b4: Tffa657IntTb4b4b4, Te6edf3localNodeNumTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Tff7b72when Tb4b4b4(Tff7b72val Te6edf3address Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3fromStringTb4b4b4(Tff7b72thisTb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3NodeAddressTb4b4b4.Te6edf3Broadcast Tff7b72-Tff7b72> Te6edf3nodeNum Tff7b72=Tff7b72= Te6edf3NodeAddressTb4b4b4.Te6edf3NODENUM_BROADCAST
Te6edf3NodeAddressTb4b4b4.Te6edf3Local Tff7b72-Tff7b72> Te6edf3nodeNum Tff7b72=Tff7b72= Te6edf3localNodeNum
Tff7b72is Te6edf3NodeAddressTb4b4b4.Te6edf3ByNum Tff7b72-Tff7b72> Te6edf3addressTb4b4b4.Te6edf3num Tff7b72=Tff7b72= Te6edf3nodeNum
Tff7b72is Te6edf3NodeAddressTb4b4b4.Te6edf3ById Tff7b72-Tff7b72> Tff7b72false
Tb4b4b4}
T8b949e/**
* Queue-stage status guard shared with callers that decide whether to arm follow-up work.
*
* Only [MessageStatus.ENROUTE] and [MessageStatus.ERROR] are owned by this transition. Terminal routing and SFPP
* statuses are applied by their dedicated state transitions.
*/
Tff7b72fun Td2a8ffshouldApplyOutgoingQueueStatusTb4b4b4(Te6edf3currentTb4b4b4: Te6edf3MessageStatus?Tb4b4b4, Te6edf3statusTb4b4b4: Te6edf3MessageStatusTb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72= Tff7b72when Tb4b4b4(Te6edf3statusTb4b4b4) Tb4b4b4{
Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTE Tff7b72-Tff7b72> Te6edf3current Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72| Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3UNKNOWN Tff7b72|Tff7b72| Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3QUEUED
Te6edf3MessageStatusTb4b4b4.Te6edf3ERROR Tff7b72-Tff7b72>
Te6edf3current Tff7b72=Tff7b72= Tff7b72null Tff7b72|Tff7b72|
Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3UNKNOWN Tff7b72|Tff7b72|
Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3QUEUED Tff7b72|Tff7b72|
Te6edf3current Tff7b72=Tff7b72= Te6edf3MessageStatusTb4b4b4.Te6edf3ENROUTE
Tff7b72else Tff7b72-Tff7b72> Tff7b72false
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.17s